﻿//******************************************************************************************************
//  HistorianCompressedStream.cs - Gbtc
//
//  Copyright © 2013, Grid Protection Alliance.  All Rights Reserved.
//
//  Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See
//  the NOTICE file distributed with this work for additional information regarding copyright ownership.
//  The GPA licenses this file to you under the Eclipse Public License -v 1.0 (the "License"); you may
//  not use this file except in compliance with the License. You may obtain a copy of the License at:
//
//      http://www.opensource.org/licenses/eclipse-1.0.php
//
//  Unless agreed to in writing, the subject software distributed under the License is distributed on an
//  "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the
//  License for the specific language governing permissions and limitations.
//
//  Code Modification History:
//  ----------------------------------------------------------------------------------------------------
//  8/10/2013 - Steven E. Chisholm
//       Generated original version of source code. 
//       
//
//******************************************************************************************************

using System;
using GSF.IO;
using openHistorian.Collections;
using GSF.SortedTreeStore.Net.Initialization;

namespace GSF.SortedTreeStore.Net.Compression
{
    public class HistorianCompressedStream
        : KeyValueStreamCompressionBase<HistorianKey, HistorianValue>
    {

        ulong m_prevTimestamp;
        ulong m_prevPointID;

        public override bool SupportsPointerSerialization
        {
            get
            {
                return true;
            }
        }

        public override int MaxCompressedSize
        {
            get
            {
                return 55; //3 extra bytes just to be safe.
            }
        }

        public override Guid CompressionType
        {
            get
            {
                return CreateHistorianCompressedStream.TypeGuid;
            }
        }

        public override void WriteEndOfStream(BinaryStreamBase stream)
        {
            stream.Write((byte)255);
        }

        public override void Encode(BinaryStreamBase stream, HistorianKey currentKey, HistorianValue currentValue)
        {
            if (currentKey.Timestamp == m_prevTimestamp
                && ((currentKey.PointID ^ m_prevPointID) < 64)
                && currentKey.EntryNumber == 0
                && currentValue.Value1 <= uint.MaxValue //must be a 32-bit value
                && currentValue.Value2 == 0
                && currentValue.Value3 == 0)
            {
                if (currentValue.Value1 == 0)
                {
                    stream.Write((byte)((currentKey.PointID ^ m_prevPointID)));
                }
                else
                {
                    stream.Write((byte)((currentKey.PointID ^ m_prevPointID) | 64));
                    stream.Write((uint)currentValue.Value1);
                }
                m_prevTimestamp = currentKey.Timestamp;
                m_prevPointID = currentKey.PointID;
                return;
            }

            byte code = 128;

            if (currentKey.Timestamp != m_prevTimestamp)
                code |= 64;

            if (currentKey.EntryNumber != 0)
                code |= 32;

            if (currentValue.Value1 > uint.MaxValue)
                code |= 16;
            else if (currentValue.Value1 > 0)
                code |= 8;

            if (currentValue.Value2 != 0)
                code |= 4;

            if (currentValue.Value3 > uint.MaxValue)
                code |= 2;
            else if (currentValue.Value3 > 0)
                code |= 1;

            stream.Write(code);

            if (currentKey.Timestamp != m_prevTimestamp)
                stream.Write7Bit(currentKey.Timestamp ^ m_prevTimestamp);

            stream.Write7Bit(currentKey.PointID ^ m_prevPointID);

            if (currentKey.EntryNumber != 0)
                stream.Write7Bit(currentKey.EntryNumber);

            if (currentValue.Value1 > uint.MaxValue)
                stream.Write(currentValue.Value1);
            else if (currentValue.Value1 > 0)
                stream.Write((uint)currentValue.Value1);

            if (currentValue.Value2 != 0)
                stream.Write(currentValue.Value2);

            if (currentValue.Value3 > uint.MaxValue)
                stream.Write(currentValue.Value3);
            else if (currentValue.Value3 > 0)
                stream.Write((uint)currentValue.Value3);

            m_prevTimestamp = currentKey.Timestamp;
            m_prevPointID = currentKey.PointID;
        }

        public override unsafe int Encode(byte* stream, HistorianKey currentKey, HistorianValue currentValue)
        {
            int size = 0;
            if (currentKey.Timestamp == m_prevTimestamp
             && ((currentKey.PointID ^ m_prevPointID) < 64)
             && currentKey.EntryNumber == 0
             && currentValue.Value1 <= uint.MaxValue //must be a 32-bit value
             && currentValue.Value2 == 0
             && currentValue.Value3 == 0)
            {
                if (currentValue.Value1 == 0)
                {
                    stream[0] = (byte)(currentKey.PointID ^ m_prevPointID);
                    size = 1;
                }
                else
                {
                    stream[0] = ((byte)((currentKey.PointID ^ m_prevPointID) | 64));
                    *(uint*)(stream + 1) = (uint)currentValue.Value1;
                    size = 5;
                }
                m_prevTimestamp = currentKey.Timestamp;
                m_prevPointID = currentKey.PointID;
                return size;
            }

            byte code = 128;

            if (currentKey.Timestamp != m_prevTimestamp)
                code |= 64;

            if (currentKey.EntryNumber != 0)
                code |= 32;

            if (currentValue.Value1 > uint.MaxValue)
                code |= 16;
            else if (currentValue.Value1 > 0)
                code |= 8;

            if (currentValue.Value2 != 0)
                code |= 4;

            if (currentValue.Value3 > uint.MaxValue)
                code |= 2;
            else if (currentValue.Value3 > 0)
                code |= 1;

            stream[0] = code;
            size = 1;

            if (currentKey.Timestamp != m_prevTimestamp)
                GSF.Compression.Write7Bit(stream, ref size, currentKey.Timestamp ^ m_prevTimestamp);

            GSF.Compression.Write7Bit(stream, ref size, currentKey.PointID ^ m_prevPointID);

            if (currentKey.EntryNumber != 0)
                GSF.Compression.Write7Bit(stream, ref size, currentKey.EntryNumber);

            if (currentValue.Value1 > uint.MaxValue)
            {
                *(ulong*)(stream + size) = currentValue.Value1;
                size += 8;
            }
            else if (currentValue.Value1 > 0)
            {
                *(uint*)(stream + size) = (uint)currentValue.Value1;
                size += 4;
            }

            if (currentValue.Value2 != 0)
            {
                *(ulong*)(stream + size) = currentValue.Value2;
                size += 8;
            }

            if (currentValue.Value3 > uint.MaxValue)
            {
                *(ulong*)(stream + size) = currentValue.Value3;
                size += 8;
            }
            else if (currentValue.Value3 > 0)
            {
                *(uint*)(stream + size) = (uint)currentValue.Value3;
                size += 4;
            }

            m_prevTimestamp = currentKey.Timestamp;
            m_prevPointID = currentKey.PointID;
            return size;
        }

        public override unsafe bool TryDecode(BinaryStreamBase stream, HistorianKey key, HistorianValue value)
        {
            byte code = stream.ReadUInt8();
            if (code == 255)
                return false;

            if (code < 128)
            {
                if (code < 64)
                {
                    key.Timestamp = m_prevTimestamp;
                    key.PointID = m_prevPointID ^ code;
                    key.EntryNumber = 0;
                    value.Value1 = 0;
                    value.Value2 = 0;
                    value.Value3 = 0;
                }
                else
                {
                    key.Timestamp = m_prevTimestamp;
                    key.PointID = m_prevPointID ^ code ^ 64;
                    key.EntryNumber = 0;
                    value.Value1 = stream.ReadUInt32();
                    value.Value2 = 0;
                    value.Value3 = 0;
                }
                m_prevTimestamp = key.Timestamp;
                m_prevPointID = key.PointID;
                return true;
            }

            if ((code & 64) != 0) //T is set
                key.Timestamp = m_prevTimestamp ^ stream.Read7BitUInt64();
            else
                key.Timestamp = m_prevTimestamp;

            key.PointID = m_prevPointID ^ stream.Read7BitUInt64();

            if ((code & 32) != 0) //E is set)
                key.EntryNumber = stream.Read7BitUInt64();
            else
                key.EntryNumber = 0;

            if ((code & 16) != 0) //V1 High is set)
                value.Value1 = stream.ReadUInt64();
            else if ((code & 8) != 0) //V1 low is set)
                value.Value1 = stream.ReadUInt32();
            else
                value.Value1 = 0;

            if ((code & 4) != 0) //V2 is set)
                value.Value2 = stream.ReadUInt64();
            else
                value.Value2 = 0;

            if ((code & 2) != 0) //V1 High is set)
                value.Value3 = stream.ReadUInt64();
            else if ((code & 1) != 0) //V1 low is set)
                value.Value3 = stream.ReadUInt32();
            else
                value.Value3 = 0;
            m_prevTimestamp = key.Timestamp;
            m_prevPointID = key.PointID;

            return true;
        }

        public override void ResetEncoder()
        {
            m_prevTimestamp = 0;
            m_prevPointID = 0;
        }
    }
}
